﻿/*
 *   CompilerItems.cs
 *   Copyright (c) 2011 - BitFox Software and Ricardo Federico Villafañes
 *   Contact: federvillaf@hotmail.com
 *   
 *   Licensing: This product is covered by Microsoft Reciprocal License (MS-RL)
 *              see https://bitfoxeval.codeplex.com/license
*/

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using BitFox.Evaluator.Functions;

namespace BitFox.Evaluator
{
    /// <summary>
    /// An enumeration with the supported types for evaluated items.
    /// </summary>
    enum EItemType
    {
        IntegerNum, DecimalNum, String, Boolean, DateTime, Name, Operator, FuncCall
    }

    /// <summary>
    /// A common interface for all possible compiler's items.
    /// </summary>
    interface IItem
    {
        /// <summary>
        /// The item is fully equivalent to the indicated one.
        /// </summary>
        /// <param name="target">The target with which to compare the actual item</param>
        /// <returns>true if item is sintactically equivalent to indicated.</returns>
        bool IsEqualTo(IItem target); 

        /// <summary>
        /// The compiled value for the item.
        /// </summary>
        object Value { get; }

        /// <summary>
        /// The item's type expressed as an enum
        /// </summary>
        EItemType Type { get; }
    }

    /// <summary>
    /// A compiler's item that holds an integer
    /// </summary>
    class IntegerItem : IItem
    {
        /// <summary>
        /// The wrapped integer value
        /// </summary>
        int v;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="v">The integer value store by this item</param>
        public IntegerItem(int v) { this.v = v; }

        /// <summary>
        /// Accessor to the value stored by this item
        /// </summary>
        public object Value
        {
            get { return v; }
        }

        /// <summary>
        /// Compare another compiler item with this
        /// </summary>
        /// <param name="target">The another compiler item</param>
        /// <returns>true if <paramref name="target"/> is an integer item and its stored value is the same than the value stored here.</returns>
        public bool IsEqualTo(IItem target)
        {
            IntegerItem itemTgt = target as IntegerItem;
            if (itemTgt == null || v != (int)itemTgt.Value)
                return false;
            return true;
        }

        /// <summary>
        /// Returns the type of value stored by this item
        /// </summary>
        public EItemType Type { get { return EItemType.IntegerNum; } }
    }

    /// <summary>
    /// A compiler's item that holds a decimal item.
    /// </summary>
    class DecimalItem : IItem
    {
        /// <summary>
        /// The value wrapped by this item
        /// </summary>
        decimal v;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="v">The decimal value to store here</param>
        public DecimalItem(decimal v) { this.v = v; }

        /// <summary>
        /// Compares this compiler item against another
        /// </summary>
        /// <param name="target">The another compiler item</param>
        /// <returns>true if <paramref name="target"/> is a decimal item and its stored value is the same than the value stored here.</returns>
        public bool IsEqualTo(IItem target)
        {
            DecimalItem itemTgt = target as DecimalItem;
            if (itemTgt == null || v != (decimal)itemTgt.Value)
                return false;
            return true;
        }

        /// <summary>
        /// Accessor to the value stored here
        /// </summary>
        public object Value
        {
            get { return v; }
        }

        /// <summary>
        /// Returns the type of the item stored here
        /// </summary>
        public EItemType Type { get { return EItemType.DecimalNum; } }
    }

    /// <summary>
    /// A compiler's item that holds a string.
    /// </summary>
    class StringItem : IItem
    {
        /// <summary>
        /// The value wreapped here
        /// </summary>
        string v;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="v">The string to wrap with this item</param>
        public StringItem(string v) { this.v = v; }

        /// <summary>
        /// Compares this compiler item against another
        /// </summary>
        /// <param name="target">The another compiler item</param>
        /// <returns>true if <paramref name="target"/> is a string item and its stored value is the same than the value stored here.</returns>
        public bool IsEqualTo(IItem target)
        {
            StringItem itemTgt = target as StringItem;
            if (itemTgt == null || v != (string)itemTgt.Value)
                return false;
            return true;
        }

        /// <summary>
        /// Accessor to the value stored here.
        /// </summary>
        public object Value
        {
            get { return v; }
        }

        /// <summary>
        /// Returns the type of the item stored here
        /// </summary>
        public EItemType Type { get { return EItemType.String; } }
    }

    /// <summary>
    /// A compiler's item that holds a boolean
    /// </summary>
    class BooleanItem : IItem
    {
        /// <summary>
        /// The value wrapped with this item
        /// </summary>
        bool v;

        /// <summary>
        /// Cosntructor
        /// </summary>
        /// <param name="v">The value that this item must store</param>
        public BooleanItem(bool v) { this.v = v; }

        /// <summary>
        /// Accessor to the value stored here
        /// </summary>
        public object Value
        {
            get { return v; }
        }

        /// <summary>
        /// Compares this compiler item against another
        /// </summary>
        /// <param name="target">The another compiler item</param>
        /// <returns>true if <paramref name="target"/> is a boolean item and its stored value is the same than the value stored here.</returns>
        public bool IsEqualTo(IItem target)
        {
            BooleanItem itemTgt = target as BooleanItem;
            if (itemTgt == null || v != (bool)itemTgt.Value)
                return false;
            return true;
        }

        /// <summary>
        /// Returns the type of the item stored here.
        /// </summary>
        public EItemType Type { get { return EItemType.Boolean; } }
    }

    /// <summary>
    /// A compiler's item that holds a datetime
    /// </summary>
    class DateTimeItem : IItem
    {
        /// <summary>
        /// The value wrapped by this item
        /// </summary>
        DateTime d;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="year">The year of the date that this item must store</param>
        /// <param name="month">The month of the date that this item must store</param>
        /// <param name="day">The day of the date that this item must store</param>
        public DateTimeItem(int year, int month, int day)
        {
            try
            {
                if (year == 0 && month == 0 && day == 0)
                    d = DateTime.MinValue;
                else
                    d = new DateTime(year, month, day); 
            }
            catch
            {
                throw new BitFoxException(ErrorCode.CompilerError, "Invalid date constant");
            }
        }

        /// <summary>
        /// Compares this compiler item against another
        /// </summary>
        /// <param name="target">The another compiler item</param>
        /// <returns>true if <paramref name="target"/> is a date item and its stored value is the same than the value stored here.</returns>
        public bool IsEqualTo(IItem target)
        {
            DateTimeItem itemTgt = target as DateTimeItem;
            if (itemTgt == null || d.CompareTo((DateTime)itemTgt.Value) != 0)
                return false;
            return true;
        }

        /// <summary>
        /// Accessor to the value store here
        /// </summary>
        public object Value
        {
            get { return d; }
        }

        /// <summary>
        /// Returns the type of the value stored by this compiler's item.
        /// </summary>
        public EItemType Type { get { return EItemType.DateTime; } }
    }

    /// <summary>
    /// A compiler's item that holds a variable's name.
    /// </summary>
    class NameItem : IItem
    {
        /// <summary>
        /// Indicates if the variable's name represented by this item is a 'dotted' one, this  is, it's a 
        /// multipart name with parts separated each of the other with dots.
        /// </summary>
        public bool IsDotted { get; private set; }

        /// <summary>
        /// The variable's name stored here.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// The variable has subindices and the number of subindices is returned by this property.
        /// </summary>
        public int IndexersCount { get; private set; }

        public NameItem(string name, int indexersCount, bool isDotted) 
        { 
            this.Name = name; 
            this.IndexersCount = indexersCount;
            IsDotted = isDotted;
        }

        /// <summary>
        /// It's illegal to get the value of a variable, at compile time. 
        /// </summary>
        /// <exception cref="InvalidOperationException">The compiler never call this property because it's illegal
        /// to get the value of a variable at compile time.</exception>
        public object Value
        {
            get { throw new InvalidOperationException(); }
        }

        /// <summary>
        /// Compares this compiler item against another
        /// </summary>
        /// <param name="target">The another compiler item</param>
        /// <returns>true if <paramref name="target"/> is a variable name item and its stored parameters are the same than the parameters stored here.</returns>
        public bool IsEqualTo(IItem target)
        {
            NameItem itemTgt = target as NameItem;
            if (itemTgt == null || Name != itemTgt.Name || IsDotted != itemTgt.IsDotted || IndexersCount != itemTgt.IndexersCount)
                return false;
            return true;
        }

        /// <summary>
        /// The type of compiler item that this item represents.
        /// </summary>
        public EItemType Type { get { return EItemType.Name; } }
    }

    /// <summary>
    /// A compiler's item that holds an operator
    /// </summary>
    class OperatorItem : IItem
    {
        /// <summary>
        /// The operator wrapped by this item
        /// </summary>
        public Operators Operator { get; private set; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="op">The operator that this item will represent</param>
        public OperatorItem(Operators op) { this.Operator = op; }

        /// <summary>
        /// Compares this compiler item against another
        /// </summary>
        /// <param name="target">The another compiler item</param>
        /// <returns>true if <paramref name="target"/> is a operator item and its stored value is the same than the value stored here.</returns>
        public bool IsEqualTo(IItem target)
        {
            OperatorItem itemTgt = target as OperatorItem;
            if (itemTgt != null && Operator != itemTgt.Operator)
                return false;
            return true;
        }

        /// <summary>
        /// Unused
        /// </summary>
        /// <exception cref="InvalidOperationException">The compiler never use this property because an 'operator' doesn't have
        /// a value at compiled time</exception>
        public object Value
        {
            get { throw new InvalidOperationException(); }
        }

        /// <summary>
        /// The type of compiler item than this item represents.
        /// </summary>
        public EItemType Type { get { return EItemType.Operator; } }
    }

    /// <summary>
    /// A compiler's item that abstract a function call.
    /// </summary>
    class FuncCallItem : IItem
    {
        /// <summary>
        /// The descriptor of the function than is called.
        /// </summary>
        public FunctionDescriptor Function { get; private set; }

        /// <summary>
        /// The count of arguments pushed to the function at call time
        /// </summary>
        public int ArgumentCount { get; private set; }

        /// <summary>
        /// The function's name is dotted, this is, it represents the name of a method of a given object.
        /// </summary>
        public bool IsDotted { get; private set; }

        /// <summary>
        /// The method's name
        /// </summary>
        public string MethodName { get; private set; }

        /// <summary>
        /// Compares this compiler item against another
        /// </summary>
        /// <param name="target">The another compiler item</param>
        /// <returns>true if <paramref name="target"/> is a callfunc item and its stored parameters are the same than the parameters stored here.</returns>
        public bool IsEqualTo(IItem target)
        {
            FuncCallItem itemTgt = target as FuncCallItem;
            if ((Function == null && itemTgt.Function != null) || (Function != null && itemTgt.Function == null))
                return false;
            if (Function != null && (Function.Name != itemTgt.Function.Name || Function.ArgumentCount != itemTgt.Function.ArgumentCount))
                return false;
            if (itemTgt == null ||
                ArgumentCount != itemTgt.ArgumentCount ||
                IsDotted != itemTgt.IsDotted ||
                MethodName != itemTgt.MethodName)
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Constructor of a func call, not dotted.
        /// </summary>
        /// <param name="method">Function's name</param>
        /// <param name="argc">The number of args to push in the call</param>
        public FuncCallItem(string method, int argc)
        {
            this.Function = null;
            this.ArgumentCount = argc;
            this.IsDotted = true;
            this.MethodName = method;
        }

        /// <summary>
        /// Constructor of func call, given descriptor.
        /// </summary>
        /// <param name="func">The descriptor of the function to call</param>
        /// <param name="argc">The number of arguments to push to function durin call</param>
        /// <param name="isDotted">The function's name is dotted.</param>
        public FuncCallItem(FunctionDescriptor func, int argc, bool isDotted) 
        { 
            this.Function = func; 
            this.ArgumentCount = argc;
            this.IsDotted = isDotted;
        }

        /// <summary>
        /// Unused
        /// </summary>
        public object Value
        {
            get { throw new InvalidOperationException(); }
        }

        /// <summary>
        /// The type of compiler item represented by this object.
        /// </summary>
        public EItemType Type { get { return EItemType.FuncCall; } }
    }
}
